using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.Services;
using Microsoft.Practices.CompositeUI.WinForms;
using Microsoft.Samples.Debugging.MdbgEngine;
using Microsoft.Practices.CompositeUI.EventBroker;
using System.Windows.Forms;
using System.Threading;
using Microsoft.Samples.Tools.Mdbg;
using Reflector;
using Reflector.CodeModel;
using Microsoft.Samples.Debugging.CorDebug;

namespace DeblectorAddIn.WorkItems
{
    public class DebuggerWorkItem:WorkItem,IMDbgIO,IMDbgIO2
    {
        [State]
        public StateObject BreakState
        {
            set { _status = value; }
            get { return _status; }
        }
        StateObject _status;
        [ServiceDependency]
        public IServiceProvider Reflector
        {
            set { _reflector = value; }
            get { return _reflector; }
        }
       
        private IServiceProvider _reflector;
        private void DebuggerWorker(object payload)
        {
            Console.SetIn(new BlockingInput(_inputSet, _shellDone));
            _shell.Start(new string[0]);
        }
        string _command;
        MDbgEngine _debugger;
        MDbgShell _shell;
        RichTextBox _richTextBox;
        AutoResetEvent _inputSet = new AutoResetEvent(false);
        AutoResetEvent _shellDone = new AutoResetEvent(false);
        AutoResetEvent _commandPresent = new AutoResetEvent(false);
        AutoResetEvent _externalToolDone = new AutoResetEvent(false);
        AutoResetEvent _commandDone = new AutoResetEvent(true);
        private void DoCommand(string s)
        {
            for (; ; )
            {
                if (_commandDone.WaitOne(0, true))
                {
                    _commandDone.Reset();
                    _command = s;
                    WriteOutputAsync("<meta>", s);
                    _commandPresent.Set();
                    _commandDone.Set();
                    break;
                }
                else
                {
                    Thread.Sleep(100);
                    System.Windows.Forms.Application.DoEvents();
                }
            }
            
        }
        public uint OffsetInCurrentIL
        {
            get { return _offsetInCurrentIL; }
        }
        uint _offsetInCurrentIL;
        bool _offsetIsExact;
        public bool OffsetIsExact
        {
            get { return _offsetIsExact; }
        }
        string _currentFunction;
        public string CurrentFunction
        {
            get { return _currentFunction;  }
        }
        void _shell_OnCommandExecuted(object sender, CommandExecutedEventArgs e)
        {
            _shell.IO.WriteOutput("<meta>", "Done.");
        }
        protected override void OnRunStarted()
        {
            _shell = new MDbgShell();
            _shell.OnCommandExecuted += new CommandExecutedEventHandler(_shell_OnCommandExecuted);

            Workspaces["bottomWorkspace"].SmartPartActivated += new EventHandler<Microsoft.Practices.CompositeUI.SmartParts.WorkspaceEventArgs>(DebuggerWorkItem_SmartPartActivated);

            ShellOutput sh = new ShellOutput();
            sh.Dock = DockStyle.Fill;
            SmartParts.Add(sh);
            _richTextBox = sh.Box;
            TabSmartPartInfo zsi = new TabSmartPartInfo();
            zsi.Title = "Shell";
            Workspaces["bottomWorkspace"].Show(sh, zsi);

            
            gui.AutoWatchWindow aww = new gui.AutoWatchWindow(_richTextBox);
            aww.Dock = DockStyle.Fill;
            SmartParts.Add(aww);
            zsi = new TabSmartPartInfo();
            zsi.Title = "Auto";
            zsi.ActivateTab = false;
            Workspaces["bottomWorkspace"].Show(aww, zsi);

            gui.Callstack csw = new gui.Callstack(_richTextBox);
            csw.Dock = DockStyle.Fill;
            SmartParts.Add(csw);
            zsi = new TabSmartPartInfo();
            zsi.Title = "Calls";
            zsi.ActivateTab = false;
            Workspaces["bottomWorkspace"].Show(csw, zsi);

            gui.ModuleWindow mw = new gui.ModuleWindow(_richTextBox);
            mw.Dock = DockStyle.Fill;
            SmartParts.Add(mw);
            zsi = new TabSmartPartInfo();
            zsi.Title = "Modules";
            zsi.ActivateTab = false;
            Workspaces["bottomWorkspace"].Show(mw, zsi);

            gui.QuickViewWindow qvw = new gui.QuickViewWindow(_richTextBox);
            qvw.Dock = DockStyle.Fill;
            SmartParts.Add(qvw);
            zsi = new TabSmartPartInfo();
            zsi.Title = "Quick View";
            zsi.ActivateTab = false;
            Workspaces["bottomWorkspace"].Show(qvw, zsi);

            ThreadPool.QueueUserWorkItem(new WaitCallback(DebuggerWorker), this);
            _shellDone.WaitOne();
            _shell.IO = this;
            _inputSet.Set();
            _debugger = _shell.Debugger;
            RootWorkItem.Services.Add<MDbgShell>(_shell);
            
            _debugger.Processes.ProcessAdded += new ProcessCollectionChangedEventHandler(Processes_ProcessAdded);
            base.OnRunStarted();

            DoCommand("help");

        }

        void DebuggerWorkItem_SmartPartActivated(object sender, Microsoft.Practices.CompositeUI.SmartParts.WorkspaceEventArgs e)
        {
            if (e.SmartPart is gui.ModuleWindow)
                OnRefreshTools("modules");
        }

        void Processes_ProcessAdded(object sender, ProcessCollectionChangedEventArgs e)
        {
            e.Process.PostDebugEvent += new PostCallbackEventHandler(Process_PostDebugEvent);
            
        }
        [EventPublication("event://refreshtool", PublicationScope.Global)]
        public event EventHandler<RefreshToolWindowEventArgs> RefreshTool;
        [EventPublication("event://debugevent", PublicationScope.Global)]
        public event EventHandler<CustomPostCallbackEventArgs> OnDebugEvent;
        protected virtual void OnRefreshTools(string reason)
        {
            if (null != RefreshTool)
                RefreshTool(this, new RefreshToolWindowEventArgs(reason));
        }
        void Process_PostDebugEvent(object sender, CustomPostCallbackEventArgs e)
        {
            switch (e.CallbackType)
            {
                case ManagedCallbackType.OnCreateProcess:
                    ((CorProcessEventArgs)e.CallbackArgs).Process.OnAssemblyLoad += new CorAssemblyEventHandler(Process_OnAssemblyLoad);
                    ((CorProcessEventArgs)e.CallbackArgs).Process.OnAssemblyUnload += new CorAssemblyEventHandler(Process_OnAssemblyUnload);
                    ((CorProcessEventArgs)e.CallbackArgs).Process.OnBreakpoint += new BreakpointEventHandler(Process_OnBreakpoint);
                    ((CorProcessEventArgs)e.CallbackArgs).Process.OnException2 += new CorException2EventHandler(Process_OnException2);
                    ((CorProcessEventArgs)e.CallbackArgs).Process.OnBreak += new CorThreadEventHandler(Process_OnBreak);
                    ((CorProcessEventArgs)e.CallbackArgs).Process.OnStepComplete +=new StepCompleteEventHandler(Process_OnStepComplete);
                    if( !BreakState.Attaching )
                        BreakState.OnBreak = false;
                    BreakState.Attaching = false;
                    break;
                case ManagedCallbackType.OnBreakpoint:
                    break;
            }
            if (null != OnDebugEvent)
                OnDebugEvent(this, e);
        }

        void Process_OnException2(object sender, CorException2EventArgs e)
        {
            if (_debugger.Options.StopOnException)
            {
                ReflectFrame(e.Thread.ActiveFrame);
                BreakState.CanStep = true;
                OnRefreshTools("break");
            }

        }
        
        void Process_OnStepComplete(object sender, CorStepCompleteEventArgs e)
        {
            ReflectFrame(e.Thread.ActiveFrame);
            BreakState.CanStep = true;
            OnRefreshTools("break");
        }
        void Process_OnBreak(object sender, CorThreadEventArgs e)
        { 
            
        }
        [EventSubscription("event://updateframe")]
        public void OnExternalUpdateFrame(object sender, gui.FrameChangedEventArgs e)
        {
           
            
            Thread t = new Thread(delegate()
           {
               ReflectFrame(e.Frame);
           });
            t.SetApartmentState(ApartmentState.MTA);
            t.IsBackground = true;
            t.Start();
            
           
        }
        private delegate void ReflectFrameDelegate(string function, string assembly,uint offset,bool exact);

        void AsyncReflectFrame(string function, string assembly,uint offset,bool exact)
        {
            FindFrameInAssemblyBrowser ffab = Services.Get<FindFrameInAssemblyBrowser>();
            IAssemblyBrowser browser = (IAssemblyBrowser)Reflector.GetService(typeof(IAssemblyBrowser));

            IMethodDeclaration mdcl = ffab.Find(assembly, function);
            if (null != mdcl)
            {
                browser.ActiveItem = mdcl;
                _offsetInCurrentIL = offset;
                _offsetIsExact = exact;
                _currentFunction = function;
            }
            IBreakTarget bt = Services.Get<IBreakTarget>();
            bt.UpdateUI();
        }
        
        public void ReflectFrame(CorFrame frame)
        {

            try
            {
                if (_debugger.Processes.Active.Threads.HaveActive)
                {
                    Microsoft.Samples.Debugging.CorDebug.NativeApi.CorDebugMappingResult res;
                    uint offset;
                    MDbgThread thr = _debugger.Processes.Active.Threads.Active;
                    MDbgFrame f = thr.CurrentFrame;
                    string functionName = null;
                    if (f.IsManaged)
                    {
                        frame.GetIP(out offset, out res);
                        functionName = f.Function.FullName;
                        Reflector.GetService(typeof(IWindowManager));
                        string partialName = frame.Function.Class.Module.Assembly.Name;
                        int last;
                        if (-1 != (last = partialName.LastIndexOf('\\')))
                            partialName = partialName.Substring(last + 1).Trim();
                        if (-1 != (last = partialName.LastIndexOf('.')))
                            partialName = partialName.Substring(0, last).Trim();
                        _richTextBox.Invoke(new ReflectFrameDelegate(AsyncReflectFrame), new object[] { functionName, partialName, offset, res == Microsoft.Samples.Debugging.CorDebug.NativeApi.CorDebugMappingResult.MAPPING_EXACT });
                    }
                    else
                    {
                        _shell.IO.WriteOutput("<meta", "<Located in native code.>");
                    }
                }
            }
            catch (Exception eee)
            {
                _shell.IO.WriteOutput("<meta>", "Exception retrieving frame:"+eee.Message);
            }
        }
        void Process_OnBreakpoint(object sender, CorBreakpointEventArgs e)
        {
            ReflectFrame(e.Thread.ActiveFrame);
            BreakState.CanStep = true;
            OnRefreshTools("break");
        }
        private delegate void AsyncUnLoadAssemblyDelegate(string name);
        void AsyncUnLoadAssembly(string name)
        {
            IAssemblyManager asmManager = (IAssemblyManager)Reflector.GetService(typeof(IAssemblyManager));
            foreach (IAssembly asm in asmManager.Assemblies)
            {
                if (asm.Name == name)
                {
                    asmManager.Assemblies.Remove(asm);
                    break;
                }
            }
        }
        void Process_OnAssemblyUnload(object sender, CorAssemblyEventArgs e)
        {
            _richTextBox.Invoke(new AsyncUnLoadAssemblyDelegate(AsyncUnLoadAssembly), new object[] { e.Assembly.Name });   
        }
        private delegate void AsyncLoadAssemblyDelegate(string name);
        void AsyncLoadAssembly(string AssemblyName)
        {
            IAssemblyManager asmManager = (IAssemblyManager)Reflector.GetService(typeof(IAssemblyManager));
            asmManager.LoadFile(AssemblyName);
            
        }
        void Process_OnAssemblyLoad(object sender, CorAssemblyEventArgs e)
        {
            _richTextBox.Invoke(new AsyncLoadAssemblyDelegate(AsyncLoadAssembly), new object[] { e.Assembly.Name });   
        }
        [EventSubscription("event://executecommand")]
        public void OnExecuteCommand(object sender, CommandEventArgs cea)
        {
            DoCommand(cea.Command);
        }
        /*
        [EventSubscription("event://attachtoprocess")]
        public void OnAttachToProcess(object sender, EventArgs e)
        {
            try
            {
                gui.AttachProcess.Item it = (gui.AttachProcess.Item)sender;
                string cmd = string.Format("a {0}", it.Pid);
                DoCommand(cmd);
            }
            catch (Exception ee)
            {
                _shell.ReportException(ee);
            }
            
        }
         */
        FPWorker m_fpWorker;
        public delegate void FPWorker(MDbgProcess process);
        public void ExecuteOnWorkerThreadIfStoppedAndBlock(FPWorker workerFunction)
        {
            
            // We should not be issuing commands while the debuggee is running.
            if (!BreakState.OnBreak && !BreakState.CanStep)
            {
                // May deadlock if called when process is running (because worker thread is only
                // available when process is stopped).
                // Only the UI thread can Go commands; so the UI thread knows if
                // the debuggee may be running; and another thread can't change this underneath it.

                // Protect us.  
                return;
            }


            m_fpWorker = workerFunction;

            // The MDbg main loop's ReadCommand is waiting on this event.
            DoToolWindow();
            _externalToolDone.WaitOne();
           
        }

        private void DoToolWindow()
        {
            // Did we get a delegate work item? If yes, we can process it right now.
            Thread t = new Thread(delegate()
            {
                if (m_fpWorker != null)
                {
                    try
                    {
                        m_fpWorker(_debugger.Processes.Active);
                    }
                    catch
                    {
                    }
                    m_fpWorker = null;
                }
                _externalToolDone.Set();
            });
            t.SetApartmentState(ApartmentState.MTA);
            t.IsBackground = true;
            t.Start();
            t.Join();
        }



        #region IMDbgIO Members
        public bool ReadCommand(out string command)
        {
            _commandPresent.WaitOne();

            
            command = _command;
            
            return true;
        }
        private void WriteOutputAsync(string outputType, string output)
        {
            _richTextBox.AppendText(output + "\n");

            _richTextBox.Select(_richTextBox.TextLength, 0);
            _richTextBox.ScrollToCaret();
        }
        private delegate void WriteOutputDelegate(string outputType, string output);
        public void WriteOutput(string outputType, string output)
        {
            _richTextBox.Invoke(new WriteOutputDelegate(WriteOutputAsync), new object[] {outputType, output });
        }

        #endregion

        #region IMDbgIO2 Members

        public void WriteOutput(string outputType, string message, int highlightStart, int highlightLen)
        {
            _richTextBox.Invoke(new WriteOutputDelegate(WriteOutputAsync), new object[] { outputType, message+"\n" });
        }

        #endregion
    }
    internal class BlockingInput:System.IO.TextReader
    {
        AutoResetEvent _evt;
        AutoResetEvent _shellStarted;
       
        public BlockingInput(AutoResetEvent evt,AutoResetEvent shellStarted)
        {
            _evt = evt;
            _shellStarted = shellStarted;
        }
        public override string ReadLine()
        {
            _shellStarted.Set();
            _evt.WaitOne();
            return "";
        }
    }
}
